package com.changgou.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.changgou.pojo.SkuInfo;
import com.changgou.search.service.EsSearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class EsSearchServiceImpl implements EsSearchService {


    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;


    @Override
    public Map search(Map<String, String> searchMap) {
        Map resultMap = new HashMap();
        if(searchMap==null){
            return resultMap;
        }

        //定义组合搜索对象
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //需求1：根据关键词进行模糊搜索 ，类似于mysql中的select * from tb_sku where name like '%手机%'
        if(StringUtils.isNotEmpty(searchMap.get("keywords"))){
            /**
             * must: 相当于and 与
             * should : 相当于or 或
             * mustnot ：相当于not 非
             */
            boolQueryBuilder.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
        }

        //需求5：根据品牌名进行精确搜索，类似于mysql中的select * from tb_sku where brand_name='华为'
        if(StringUtils.isNotEmpty(searchMap.get("brand"))){
            boolQueryBuilder.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand") ));
        }

        //需求6：根据分类名称进行精确搜索，类似于mysql的select * from tb_sku where category_name='耳机'
        if(StringUtils.isNotEmpty(searchMap.get("categoryName"))){
            boolQueryBuilder.filter(QueryBuilders.termQuery("categoryName", searchMap.get("categoryName")));
        }

        //需求7：根据规格进行精确搜索，类似于mysql的select * from tb_sku where spec_color='红色'
        if(searchMap.size()>0){
            for(String key : searchMap.keySet()){
                if(key.startsWith("spec_")){ //这里的key的值可能是这样： spec_颜色：红色， spec_像素：1200万
                    String specName = key.substring(5);
                    String specVal = searchMap.get(key);
                    specVal = specVal.replace("%2B", "+");
                    //由于specMap.系列的列在ES中是text类型，精确搜索时需要将列临时转为keyword类型
                    boolQueryBuilder.filter(QueryBuilders.termQuery("specMap."+specName+".keyword", specVal));
                }
            }
        }

        //需求8：根据价格范围进行搜索，类似于mysql中的select * from tb_sku where price between 100 and 1000
        if(StringUtils.isNotEmpty(searchMap.get("price"))){
            String price = searchMap.get("price"); //这里的price对应的值形如： 2000-5000
            String[] split = price.split("-");
            if(split.length==2){
                String lowPrice = split[0]; //价格区间里值小的价格
                String highPrice = split[1];//价格区间里值大的价格
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(lowPrice).lte(highPrice));
            }
        }


        //定义顶级搜索对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        nativeSearchQueryBuilder.withQuery(boolQueryBuilder); //把组合搜索对象封装进来

        //需求2：根据品牌名进行聚合， 类似于mysql中的select brand_name from tb_sku group by brand_name
        //需求2.1：设置品牌分组名
        String brandGroup = "brandGroup";
        TermsAggregationBuilder brandGroupBuilder = AggregationBuilders.terms(brandGroup).field("brandName");
        nativeSearchQueryBuilder.addAggregation(brandGroupBuilder);

        //需求3：根据分类名进行聚合，类似于mysql中的select category_name from tb_sku group by category_name
        //需求3.1：设置分类分组名
        String cateGroup = "cateGroup";
        TermsAggregationBuilder cateGroupBuilder = AggregationBuilders.terms(cateGroup).field("categoryName");
        nativeSearchQueryBuilder.addAggregation(cateGroupBuilder);

        //需求4：根据规格进行聚合，类似于mysql中的select spec from tb_sku group by spec
        //需求4.1：设置规格分组名
        String specGroup = "specGroup";
        //由于spec列在ES中是text类型，精确聚合时需要将列临时转为keyword类型
        TermsAggregationBuilder specGroupBuilder = AggregationBuilders.terms(specGroup).field("spec.keyword");
        nativeSearchQueryBuilder.addAggregation(specGroupBuilder);


        //需求9：根据价格、新品、销量等进行排序，类似于mysql中的select * from tb_sku order by price desc|asc
        if(StringUtils.isNotEmpty(searchMap.get("sortField")) && StringUtils.isNotEmpty(searchMap.get("sortRule"))){
            String sortField = searchMap.get("sortField"); //排序的字段，根据谁来排序
            String sortRule = searchMap.get("sortRule"); //排序的规则，是ASC还是DESC
            if("DESC".equalsIgnoreCase(sortRule)){
                nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField).order(SortOrder.DESC));
            } else {
                nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField).order(SortOrder.ASC));

            }
        }

        //需求10：对搜索结果进行分页
        int pageNum = 1;
        int pageSize = 20;
        if(StringUtils.isNotEmpty(searchMap.get("pageNum"))){
            pageNum = Integer.valueOf(searchMap.get("pageNum"));
        }
        if(StringUtils.isNotEmpty(searchMap.get("pageSize"))){
            pageSize = Integer.valueOf(searchMap.get("pageSize"));
        }
        //在ES中分页pageNo从0开始，所以传入的参数要减1
        nativeSearchQueryBuilder.withPageable(PageRequest.of(pageNum-1, pageSize));


        //需求11：搜索结果高亮显示
        //需求11.1：设置高亮的标签
        HighlightBuilder.Field highlightField = new HighlightBuilder.Field("name").preTags("<span style='color:red'>").postTags("</span>");
        nativeSearchQueryBuilder.withHighlightFields(highlightField);


        //执行搜索，用下方的搜索构造方法目的是方便处理分页、高亮、聚合的结果数据
        AggregatedPage<SkuInfo> searchResult = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
                SearchHit[] hits = searchResponse.getHits().getHits(); //搜索命中的结果，结果里是大量的JSON集合
                long total = searchResponse.getHits().getTotalHits(); //搜搜命中的条数
                List<T> skuList = new ArrayList<>();
                if(total>0){
                    for (SearchHit hit : hits) {
                        String skuJson = hit.getSourceAsString(); //搜索结果商品的JSON字符串
                        SkuInfo skuInfo = JSON.parseObject(skuJson, SkuInfo.class);//将商品JSON字符串转换为商品类型对象

                        //需求11.2：处理高亮字段的搜索结果，再设置到sku对象中
                        String skuName = hit.getHighlightFields().get("name").getFragments()[0].toString();
                        skuInfo.setName(skuName);

                        skuList.add((T)skuInfo);
                    }
                }
                return new AggregatedPageImpl<T>(skuList, pageable, total, searchResponse.getAggregations());
            }
        });

        //需求2.2：根据品牌分组名取出品牌分组数据
        StringTerms brandTerms = (StringTerms) searchResult.getAggregation(brandGroup);
        List<StringTerms.Bucket> brandBuckets = brandTerms.getBuckets();
        List<String> brandList = new ArrayList<>();
        if(brandBuckets!=null && brandBuckets.size()>0){
//            for (StringTerms.Bucket brandBucket : brandBuckets) {
//                String brandName = brandBucket.getKeyAsString(); //取出聚合数据里的分组名
//                brandList.add(brandName);
//            }
            brandList = brandBuckets.stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
        }
        
        //需求3.2：根据分类名称取出分类分组数据
        StringTerms cateTerms = (StringTerms) searchResult.getAggregation(cateGroup);
        List<StringTerms.Bucket> cateBuckets = cateTerms.getBuckets();
        List<String> cateList = new ArrayList<>();
        if(cateBuckets!=null && cateBuckets.size()>0){
            cateList = cateBuckets.stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
        }
        
        //需求4.2：根据规格分组名取出规格分组数据
        StringTerms specTerms = (StringTerms) searchResult.getAggregation(specGroup);
        List<StringTerms.Bucket> specBuckets = specTerms.getBuckets();
        Map<String, Set<String>> specMap = new HashMap<>();
        if(specBuckets!=null && specBuckets.size()>0){
            /**
             * 比如有四条聚合后的规格JSON的值如下：
             * {'颜色': '枪色', '尺码': '单光300+'}
             * {'颜色': '枪色', '尺码': '单光250+'}
             * {'颜色': '黑色', '尺码': '单光150+'}
             * {'颜色': '枪色', '尺码': '单光250+'}
             *
             * 这些值希望转成页面方便解析的值，还需要进一步去重：
             * 希望拿到的数据结构是这样：
             * { '颜色':['枪色','黑色'],'尺码':['单光150+','单光250+','单光300+'] }
             *
             */
            for (StringTerms.Bucket specBucket : specBuckets) {
                //规格对应的JSON字符串，需要转换为MAP对象
                String specJson = specBucket.getKeyAsString();
                Map<String,String> map = JSON.parseObject(specJson, Map.class);
                if(map.size()>0){
                    Set<String> set = null;
                    for(String key : map.keySet()){
                        String specName = key;
                        String specVal = map.get(key);
                        if(!specMap.containsKey(key)){
                            set = new HashSet<>();
                        } else {
                            set = specMap.get(key);
                        }
                        set.add(specVal);
                        specMap.put(key, set);
                    }
                }
            }
            
        }
        

        resultMap.put("rows", searchResult.getContent());//所有搜索结果记录列表，如果不设置分页这里默认是10条数据
        resultMap.put("total", searchResult.getTotalElements());//搜索结果条数
        resultMap.put("totalPage", searchResult.getTotalPages());//分页页数，如果不设置分页这里默认只有1页
        resultMap.put("brandList", brandList);//品牌聚合结果列表，最终到页面的JSON值类似于['华为','三星','小米']
        resultMap.put("cateList", cateList);//分类聚合结果列表，最终到页面的JSON值类似于['手机','二手手机']
        resultMap.put("specList", specMap); //规格聚合结果列表，最终到页面的JSON值类似于{ '颜色':['枪色','黑色'],'尺码':['单光150+','单光250+','单光300+'] }
        resultMap.put("pageNum",pageNum );//分页页码
        resultMap.put("pageSize", pageSize );//每页显示的条数
        return resultMap;
    }
}
